package com.jin.crud.common.utils;

import com.alibaba.fastjson.JSON;
import com.jin.crud.bean.LoginUser;
import com.jin.crud.bean.SysUser;
import com.jin.crud.common.utils.ip.AddressUtils;
import com.jin.crud.common.utils.ip.IpUtils;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author yuxuejin
 * @Date 2021/11/26 16:15
 * @Description: token工具类
 */
@Component
public class JwtUtils {

    // 令牌自定义标识
    @Value("${jwt.header}")
    private String header;

    // 令牌秘钥
    @Value("${jwt.secret}")
    private String secret;

    private final Map<String, String> tokenMap = new HashMap<>();
    private final Map<String, String> refreshTokenMap = new HashMap<>();

    @Value("${jwt.access_token_expiration}")
    private Long accessTokenExpiration;

    @Value("${jwt.refresh_token_expiration}")
    private Long refreshTokenExpiration;

    private final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;

    public LoginUser getLoginUserFromToken(String token) {
        final Claims claims = getClaimsFromToken(token);
        if (claims == null) {
            return null;
        }
        String userAccount = claims.getSubject();
        String userId = claims.get("userId").toString();
        //String authorities = claims.get("authorities").toString();
        //Set<GrantedAuthority> grantedAuthorities = Arrays.stream(authorities.split(","))
        //        .map(SimpleGrantedAuthority::new)
        //        .collect(Collectors.toSet());
        return LoginUser.builder()
                .userId(userId)
                .userAccount(userAccount)
                //.authorities(grantedAuthorities)
                .build();
    }

    /**
     * 令牌是否过期
     */
    public Boolean isAccessTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        if (expiration == null) {
            return true;
        }
        return expiration.before(new Date());
    }

    /**
     * 从令牌中获取到期日期
     */
    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    /**
     * 利用jwt解析token信息.
     *
     * @param token 要解析的token信息
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 设置用户代理信息
     *
     * @param loginUser 登录信息
     */
    private void setUserAgent(LoginUser loginUser) {
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
        loginUser.setBrowser(userAgent.getBrowser().getName());
        loginUser.setOs(userAgent.getOperatingSystem().getName());
    }

    /**
     * 刷新令牌有效期
     */
    public String refreshToken(String refreshToken) {
        if (isRefreshTokenExpired(refreshToken)) {
            return null;
        }
        String token;
        synchronized (this) {
            final LoginUser loginUser = getLoginUserFromToken(refreshToken);
            if (loginUser == null) {
                return null;
            }
            String userId = loginUser.getUserId();
            token = tokenMap.get(userId);
            if (isAccessTokenExpired(token)) {
                token = generateAccessToken(loginUser);
                tokenMap.put(userId, token);
            }
        }
        return token;
    }

    /**
     * 生成访问令牌
     */
    public String generateAccessToken(LoginUser loginUser) {

        String token = generateToken(loginUser, accessTokenExpiration);
        tokenMap.put(loginUser.getUserId(), token);
        return token;
    }

    /**
     * 生成刷新令牌
     */
    public String generateRefreshToken(LoginUser loginUser) {
        String refreshToken = generateToken(loginUser, refreshTokenExpiration);
        refreshTokenMap.put(loginUser.getUserId(), refreshToken);
        return refreshToken;
    }

    /**
     * 令牌是否过期
     */
    public Boolean isRefreshTokenExpired(String refreshToken) {
        Date expiration;
        try {
            expiration = Jwts.parser().setSigningKey(secret).parseClaimsJws(refreshToken).getBody().getExpiration();
        } catch (ExpiredJwtException e) {
            return true;
        }
        return expiration.before(new Date());
    }

    /**
     * 生成到期日期
     */
    private Date generateExpirationDate(long expiration) {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    /**
     * 生成声明
     */
    private Map<String, Object> generateClaims(LoginUser loginUser) {
        Map<String, Object> claims = new HashMap<>(8);
        claims.put("userId", String.valueOf(loginUser.getUserId()));
        //claims.put("authorities", loginUser.getAuthorities()
        //        .stream()
        //        .map(GrantedAuthority::getAuthority)
        //        .collect(Collectors.joining(",")));
        claims.put("subject", loginUser.getUserAccount());
        return claims;
    }

    private String generateToken(LoginUser loginUser, long expiration) {
        setUserAgent(loginUser);
        Map<String, Object> claims = generateClaims(loginUser);
        String username = loginUser.getUsername();
        String userId = String.valueOf(loginUser.getUserId());
        SysUser user = loginUser.getUser();
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(JSON.toJSONString(user))
                .setId(userId)
                .setIssuedAt(new Date())
                .setExpiration(generateExpirationDate(expiration))
                .compressWith(CompressionCodecs.DEFLATE)
                .signWith(SIGNATURE_ALGORITHM, secret)
                .compact();
    }

    /**
     * 检查有效令牌
     */
    public boolean checkValidToken(String token, String userId) {
        return tokenMap.containsKey(userId) && tokenMap.get(userId).equals(token);
    }

    public LoginUser getUserDetailFromAuthContext() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return (LoginUser) authentication.getPrincipal();
    }

    public void removeToken(String userId) {
        if (tokenMap.remove(userId) != null) {
            refreshTokenMap.remove(userId);
        }
    }
}
